타입은 문자열(string), 불린(boolean), 오브젝트 처럼 이름과 구조를 가진 것을 말한다.
정적 타입 체커는 일반적으로 이름이나 구조를 이용하여 타입의 다름을 구분한다.
C++, Java와 같은 언어는 Nominal 타입 시스템을 이용한다.
이름이 다를 경우 다른 타입이므로 다음과 같은 코드는 불가능하다.
// pseudo code이다
class A {
method(input: string): number {...}
}
class B {
method(input: string): number {...}
}
let a: A = new B(); // 에러
Ocaml, Haskell, Javascript는 Structural 타입 시스템을 이용한다.
이름이 다르더라도 구조가 같으므로 다음과 같은 코드도 괜찮다.
// pseudo code이다
class A {
method(input: string): number {...}
}
class B {
method(input: string): number {...}
}
let a: A = new B(); // 통과
대신 아래처럼 구조가 바뀌면 다른 타입으로 인식한다.
// pseudo code이다
class A {
method(input: string): number {...}
}
class B {
method(input: number): number {...}
}
let a: A = new B(); // 에러
TypeScript는 기본적으로 Structural 타입 시스템이다. TypeScript는 결국 JavaScript로 변환되기 때문에 JavaScript의 성질을 따를 수 밖에 없기 때문이다.
하지만 약간의 코드로 Nominal 타입의 이점을 가져갈 수 있다. 제약 조건 역할을 하는 특수한 속성(__brand
, __
는 제약을 위한 속성의 이름의 컨벤션이다.)과 Intersectional 타입을 이용하여 ValidatedInputString
에 우리가 원하는 문자열만 저장할 수 있도록 제한할 수 있다.
type ValidatedInputString = string & { __brand: "User Input Post Validation" }
이제 제약조건을 만족시키는 문자열로 변환하는 함수를 만들어보자
const validateUserInput = (input: string) => {
const simpleValidatedInput = input.replace(/\</g, "<=")
return simpleValidatedInput as ValidatedInputString
}
제약조건을 만족하는 문자열만 인자로 받아 출력하는 함수를 만들어보자
const printName = (name: ValidatedInputString) => {
console.log(name)
}
원하지 않는 문자열이 전달됐을 때 validateUserInput
을 통해 변환되어 출력되는 것을 볼 수 있다.
const input = "\n<script>alert('bobby tables')</script>"
const validated = validateUserInput(input)
printName(validated)
문자열을 그냥 출력하려할 경우 타입 체커가 에러를 뱉게 된다.
printName(input) // 에러
TypeScript를 이용한 nominal 타입에 관심이 있다면 다음 두 글을 읽어보자